package preprocess;

import java.util.HashMap;
import java.util.List;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.Text;
import org.dom4j.io.SAXReader;

import ui.xmlVisualization.XMLTree;

/**
 * 
 * contract the xml document
 * 
 * @author huangcd
 * 
 */
public class XMLContracter
{
    /**
     * whether to delete the empty text nodes(they always deleted, if you want
     * only to delete empty text nodes, the argument 'tag' show be "{@code
     * EMPTY_TEXT}")
     */
    public final static int          EMPTY_TEXT = 0X00;
    /**
     * whether to delete all the text nodes
     */
    public final static int          TEXT       = 0X01;
    /**
     * whether to delete all the attribute nodes
     */
    public final static int          ATTRIBUTE  = 0X02;
    /**
     * whether to delete all the repetition nodes
     */
    public final static int          REPETITION = 0X04;
    /**
     * whether to delete all the nesting nodes
     */
    public final static int          NESTING    = 0X08;
    private HashMap<String, Element> map        = new HashMap<String, Element>();

    /**
     * 约简一个XML文档，可以约简的内容有文本（TEXT)、属性（及命名空间等）（ATTRIBUTE)、重复节点（REPETITION：
     * 同一个父节点的同名节点）、 嵌套节点（NESTING：父子节点名字相同）。调用函数的时候，把希望进行的约简行为用“|”连接，赋值给tag：
     * 如果希望约简文本、重复节点、嵌套节点，则tag的值应该是
     * "XMLContracter.TEXT | XMLContracter.REPETITION | XMLContracter.NESTING"
     * 
     * @param doc
     * @param tag
     * @return
     */
    public Document contract(Document doc, int tag)
    {
        Element root = doc.getRootElement();
        if ((tag & NESTING) != 0)
            reduceNesting(root);
        reduceOthers(root, (Integer.MAX_VALUE ^ NESTING) & tag);
        return doc;
    }

    /**
     * reduce the nesting node, a nest-node is a non-leaf node whose label is
     * the same with its parent
     * 
     * @param element
     */
    private void reduceNesting(Element element)
    {
        String parentName = element.getName();
        for (int i = 0; i < element.nodeCount(); i++)
        {
            Node node = element.node(i);
            if (node instanceof Element)
            {
                Element child = (Element) node;
                String name = child.getName();
                if (name.equals(parentName))
                {
                    for (Object obj : child.elements())
                    {
                        Element e = (Element) obj;
                        child.remove(e);
                        element.add(e);
                    }
                }
                else
                {
                    reduceNesting(child);
                }
            }
            else
            {}
        }
    }

    /**
     * reduce other contents of the xml document
     * 
     * @param element
     * @param tag
     */
    @SuppressWarnings("unchecked")
    private void reduceOthers(Element element, int tag)
    {
        map.clear();
        // delete attributes of the element
        if ((tag & ATTRIBUTE) != 0)
        {
            List<Attribute> list = element.attributes();
            while (!list.isEmpty())
            {
                element.remove(list.get(0));
            }
        }
        for (int i = 0; i < element.nodeCount(); i++)
        {
            Node node = element.node(i);
            // combine those elements that have same names
            if (node instanceof Element)
            {
                if ((tag & REPETITION) != 0)
                {
                    Element elem = (Element) node;
                    String name = elem.getName();
                    if (map.containsKey(name))
                    {
                        Element replaceElem = map.get(name);
                        element.remove(elem);
                        i--;
                        for (int j = 0; j < elem.nodeCount();)
                        {
                            Node obj = elem.node(0);
//                            Element e = (Element) obj;
                            elem.remove(obj);
                            replaceElem.add(obj);
                        }
                    }
                    else
                    {
                        map.put(name, elem);
                    }
                }
            }
            // delete text node
            else if (node instanceof Text)
            {
                if ((tag & TEXT) != 0)
                {
                    element.remove(node);
                    i--;
                }
                else if (node.getText().trim().equals(""))
                {
                    element.remove(node);
                    i--;
                }
            }
            // delete other nodes (like namespace)
            else
            {
                if ((tag & ATTRIBUTE) != 0)
                {
                    element.remove(node);
                    i--;
                }
            }
        }
        for (int i = 0; i < element.nodeCount(); i++)
        {
            Node node = element.node(i);
            if (node instanceof Element)
                reduceOthers((Element) node, tag);
        }
    }

    /**
     * tester and demo for this class
     * 
     * @param args
     * @throws DocumentException
     */
    public static void main(String[] args) throws DocumentException
    {
        SAXReader reader = new SAXReader();
        Document document = reader
        // .read("treeT1.xml");
                .read("treeT1.xml");
        XMLTree.showTree(document);
        XMLContracter dc = new XMLContracter();
        dc.contract(document, XMLContracter.ATTRIBUTE | XMLContracter.NESTING
                | XMLContracter.REPETITION );
        XMLTree.showTree(document);
    }
}
